﻿/***********************************************************************************\
| Copyright (c) 2011, Bashar Al-Rawi                                                |
| All rights reserved.                                                              |
|                                                                                   |
| Redistribution and use in source and binary forms, with or without                |
| modification, are permitted provided that the following conditions are met:       |
|    * Redistributions of source code must retain the above copyright               |
|      notice, this list of conditions and the following disclaimer.                |
|    * Redistributions in binary form must reproduce the above copyright            |
|      notice, this list of conditions and the following disclaimer in the          |
|      documentation and/or other materials provided with the distribution.         |
|    * Neither the name of the <organization> nor the                               |
|      names of its contributors may be used to endorse or promote products         |
|      derived from this software without specific prior written permission.        |
|                                                                                   |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND   |
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED     |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE            |
| DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY                |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;      |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND       |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT        |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS     |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                      |
\***********************************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Windows.Resources;
using System.Xml.Linq;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Arabic.Utils
{
    public class ArabicCharacter : Character
    {
        private static IDictionary<string, string> charToRealChar;
        private static IDictionary<string, string> realCharToChar;
        private static IDictionary<string, string> connectToPrevChar;
        private static IDictionary<string, string> connectToNextChar;
        private static int LAMUNICODE = 0xFEDD;
        private static int UNDERSCORE = 0x640;
        private static int[] ALFUNICODE = { 0xFE8D, 0xFE81, 0xFE83, 0xFE87 };
        private static int[] LAMALFUNICODE = { 0xFEFB, 0xFEF5, 0xFEF7, 0xFEF9 };
        public int DefaultUnicodeCode { get; set; }
        private static bool Is7araka(Character ch)
        {
            ArabicCharacter ar = ch as ArabicCharacter;
            return ar != null && (ar.DefaultUnicodeCode >= 0x64B && ar.DefaultUnicodeCode < 0x660);
        }
        private static bool IsSameChar(Character ch)
        {
            ArabicCharacter ar = ch as ArabicCharacter;
            return ar != null && (ar.DefaultUnicodeCode == UNDERSCORE || Is7araka(ch));
        }
        private static bool IsLam(Character ch)
        {
            ArabicCharacter ar = ch as ArabicCharacter;
            return ar != null && ar.DefaultUnicodeCode == LAMUNICODE;
        }
        private static bool IsAlf(Character ch)
        {
            ArabicCharacter ar = ch as ArabicCharacter;
            if (ar != null)
            {
                foreach (int alf in ALFUNICODE)
                {
                    if (ar.DefaultUnicodeCode == alf)
                        return true;
                }
            }
            return false;
        }

        private static void ParseKeys(XDocument document)
        {
            var keys = from key in document.Descendants("Key")
                       where (string)key.Attribute("Lang") == "AR"
                       select key;
            Regex rx = new Regex(@"\\[uU]([0-9A-F]{4})");

            foreach (var key in keys)
            {
                string realCh = null;
                if (key.Attribute("Base") != null)
                {
                    realCh = rx.Replace
                    (
                        key.Attribute("Base").Value,
                        delegate(Match match)
                        {
                            return ((char)Int32.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString();
                        }
                    );
                }

                string ch = rx.Replace
                    (
                        key.Attribute("Display").Value,
                        delegate(Match match)
                        {
                            return ((char)Int32.Parse(match.Value.Substring(2), NumberStyles.HexNumber)).ToString();
                        }
                    );

                if (key.Attribute("ConnectsToPrev") != null)
                {
                    bool canConnectToPrevChar = bool.Parse(key.Attribute("ConnectsToPrev").Value);
                    if (canConnectToPrevChar)
                    {
                        connectToPrevChar.Add(ch, ch);
                    }
                }
                if (key.Attribute("ConnectsToNext") != null)
                {
                    bool canConnectToNextChar = bool.Parse(key.Attribute("ConnectsToNext").Value);
                    if (canConnectToNextChar)
                    {
                        connectToNextChar.Add(ch, ch);
                    }
                }

                if (!string.IsNullOrEmpty(realCh))
                {
                    charToRealChar.Add(ch, realCh);
                    realCharToChar.Add(realCh, ch);
                }
            }
        }

        public static bool IsArabic(char ch)
        {
            int unicode = (int)ch;
            if (unicode >= 0x600 && unicode <= 0x6FF || unicode >= 0xFE70 && unicode <= 0xFEFF)
                return true;
            return false;
        }

        public static new void Initialize()
        {
            connectToPrevChar = new Dictionary<string, string>();
            connectToNextChar = new Dictionary<string, string>();
            charToRealChar = new Dictionary<string, string>();
            realCharToChar = new Dictionary<string, string>();
            Assembly assembly = typeof(ArabicCharacter).Assembly;
            using (Stream xmlStream = assembly.GetManifestResourceStream("Arabic.Utils.Characters.xml"))
            {
                XDocument document = XDocument.Load(xmlStream);
                ParseKeys(document);
            }
        }
        public ArabicCharacter() { } // needed for deserialization
        public ArabicCharacter(char ch) : this(ch, null, null) { }
        public ArabicCharacter(char ch, Character prev, Character next)
        {
            this.Lang = KeyCode.AR;
            if (!realCharToChar.ContainsKey(ch.ToString()))
                this.DefaultChar = ch.ToString();
            else
                this.DefaultChar = realCharToChar[ch.ToString()];

            if (KeyCodeHelper.Translate(this.DefaultChar) == null)
                this.DefaultUnicodeCode = (int)ch;
            else
            {
                this.DefaultUnicodeCode = (int) KeyCodeHelper.Translate(this.DefaultChar);
            }
            this.Prev = prev;
            this.Next = next;
        }

        public override string Char
        {
            get
            {
                if (ArabicCharacter.charToRealChar.ContainsKey(this.DefaultChar))
                {
                    return ArabicCharacter.charToRealChar[this.DefaultChar];
                }
                else
                {
                    return ((char)this.DefaultUnicodeCode).ToString();
                }
            }

        }


        private bool CanConnectToPrevChar(string ch)
        {
            return connectToPrevChar.ContainsKey(ch);
        }

        private bool CanConnectToNextChar(string ch)
        {
            return connectToNextChar.ContainsKey(ch);
        }

        public override string DisplayChar
        {
            get
            {
                // if a letter is a haraka, we need to skip it
                Character prev = Prev;
                Character next = Next;
                while (prev != null && ArabicCharacter.Is7araka(prev))
                    prev = prev.Prev;
                while (next != null && ArabicCharacter.Is7araka(next))
                    next = next.Next;

                bool canConnectToPrevChar = this.CanConnectToPrevChar(this.DefaultChar) &&
                    prev != null && this.CanConnectToNextChar(prev.DefaultChar);
                bool canConnectToNextChar = this.CanConnectToNextChar(this.DefaultChar) &&
                    next != null && this.CanConnectToPrevChar(next.DefaultChar);
                int offset = 0;
                int unicode = this.DefaultUnicodeCode;
                if (IsLam(this) && IsAlf(this.Next))
                {
                    offset = canConnectToPrevChar ? 1 : 0;
                    int alfIndex = GetAlfIndex(this.Next);
                    if (alfIndex == -1)
                        throw new ArgumentOutOfRangeException("Cannot find Alf index");
                    return ((char)(LAMALFUNICODE[alfIndex] + offset)).ToString();
                }
                else if (IsAlf(this) && IsLam(this.Prev))
                {
                    return string.Empty;
                }
                else if (IsSameChar(this))
                {
                    return this.DefaultChar;
                }
                else
                {
                    if (canConnectToNextChar && canConnectToPrevChar)
                    {
                        offset = 3;
                    }
                    else if (canConnectToNextChar)
                    {
                        offset = 2;
                    }
                    else if (canConnectToPrevChar)
                    {
                        offset = 1;
                    }
                    return ((char)(this.DefaultUnicodeCode + offset)).ToString();
                }
            }
        }

        private int GetAlfIndex(Character character)
        {
            ArabicCharacter ar = character as ArabicCharacter;
            if (ar != null)
            {
                for (int i = 0; i < ALFUNICODE.Length; i++)
                    if (ar.DefaultUnicodeCode == ALFUNICODE[i])
                        return i;
            }
            return -1;
        }
    }
}
